'''
Author: duliang thinktanker@163.com
Date: 2025-08-27 20:10:41
LastEditors: duliang thinktanker@163.com
LastEditTime: 2025-10-31 15:54:21
FilePath: \watergrassyolo\myyolo.py
Description: 
'''
import cv2
import torch
from ultralytics import YOLO
import numpy as np
import os
import sys
from typing import List, Tuple, Optional, Dict, Any
import sqlite3
import time
import shutil
import save_plt_to_dataimage

WARNTXTPATH = r'D:\myGitee\control-net\data\miniindex\首页警告.txt'
YOLODBPATH = r'./data/yolo.db'
MINIPROPATH = r'D:\myGitee\control-net\data\miniindex\ratio.jpg'
MINIPROPREPATH = r'D:\myGitee\control-net\data\miniindex\ratiopre.jpg'
MINIPROLATESTPATH = r'D:\myGitee\control-net\data\miniindex\latest.jpg'
MODELPATH = r"./models/best.pt"
saveImagePerMin = 5  #每？分钟保存一张图片
# 水平线上框判定允许的偏差
HLINEDEVIATION = 50
# CONFTHRESHOLD = 0.2
CONFTHRESHOLD = 0.4
IOUTHRESHOLD = 0.5


def check_watergrass_ratio(limit=20):
    conn = sqlite3.connect(YOLODBPATH)
    cursor = conn.cursor()
    cursor.execute(
        "SELECT count,wglarea,imgarea,guessarea,timestamp FROM watergrass ORDER BY timestamp DESC LIMIT ?",
        (limit, ))
    rows = cursor.fetchall()
    conn.close()

    sum_area = 0
    sum_img_px = 0
    start_time = rows[-1][-1]
    end_time = rows[0][-1]
    sum_img_px = rows[0][2]
    for row in rows:
        sum_area += row[1]
    current_rate = rows[0][1] / sum_img_px * 100
    aver_rate = sum_area / sum_img_px * 100
    guess_rate = rows[0][3] / sum_img_px * 100
    # total_rate = sum_area / sum_img_px * 100
    print(
        f"{time.strftime('%Y-%m-%d', time.localtime(start_time))}至{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time))}"
    )
    print(f"总占比: {aver_rate:.2f}%")
    # 检查占比，保存到告警
    # area_ratio = (total_area / (img_width * img_height)
    #                 ) * 100 if (img_width * img_height) > 0 else 0

    time_temp = f"{time.strftime('%H:%M:%S', time.localtime(start_time))}至{time.strftime('%H:%M:%S', time.localtime(end_time))}"
    now_time = int(time.time())
    guess_rate = rows[0][3] / sum_img_px * 100
    warn_content = f"🌿{limit}分钟入栅{aver_rate:.2f}%👁️‍🗨识别量{current_rate:.2f}%🎲预估量{guess_rate:.2f}%"
    temp = ''
    if aver_rate > 100:
        temp = ('警告', now_time, f'[{time_temp} 水草😡危，紧急清理  {warn_content}]',
                '首页警告')
    elif aver_rate > 50:
        temp = ('警告', now_time, f'[{time_temp} 水草😠高，立刻清理  {warn_content}]',
                '首页警告')
    elif aver_rate > 20:
        temp = ('警告', now_time, f'[{time_temp} 水草😐中，注意清理  {warn_content}]',
                '首页警告')
    else:
        temp = ('警告', now_time, f'[{time_temp} 水草😀正常，随意清理 {warn_content}]',
                '首页警告')
    print(temp)
    with open(WARNTXTPATH, 'w', encoding='utf8') as f:
        f.write(str(temp))
    save_plt_to_dataimage.save_plt(12)
    save_plt_to_dataimage.save_plt(1, 'minute')


class MyYOLO:

    def __init__(self,
                 model_path: Optional[str] = None,
                 device: Optional[str] = None):
        """
        初始化YOLO模型
        :param model_path: 模型路径
        :param device: 运行设备 ('cpu' 或 'cuda')
        """
        if device is None:
            self.device = torch.device(
                "cuda" if torch.cuda.is_available() else "cpu")
        else:
            self.device = torch.device(device)

        self.model = None
        self.class_names = {}

        # 添加计数线相关属性
        self.counting_line = None  # 计数线坐标 [x1, y1, x2, y2]
        self.count_up = 0  # 上行计数
        self.count_down = 0  # 下行计数
        self.object_tracks = {}  # 对象轨迹 {track_id: [(x, y), ...]}
        self.track_id = 0  # 跟踪ID计数器
        self.class_line_counts = {}  # 各类别经过计数线次数
        self.count = 0  # 用于单图像处理的计数器
        self.guessarea = 0

        if model_path:
            self.load_model(model_path)

    def load_model(self, model_path: str) -> None:
        """
        加载YOLO模型
        :param model_path: 模型路径
        """
        try:
            if not os.path.exists(model_path):
                raise FileNotFoundError(f"模型文件不存在: {model_path}")

            self.model = YOLO(model_path)
            self.model = self.model.to(self.device)
            self.class_names = self.model.names
            print(f"模型加载成功，使用设备: {self.device}")
        except Exception as e:
            print(f"加载模型时出错: {str(e)}")
            raise

    def detect_image(
            self,
            image_path: str,
            conf_threshold: float = CONFTHRESHOLD,
            iou_threshold: float = IOUTHRESHOLD) -> Tuple[np.ndarray, Any]:
        """
        识别单张图片
        :param image_path: 图片路径
        :param conf_threshold: 置信度阈值
        :param iou_threshold: IOU阈值
        :return: 处理后的图片和检测结果
        """
        if self.model is None:
            raise ValueError("模型未加载，请先调用load_model方法")

        if not os.path.exists(image_path):
            raise FileNotFoundError(f"图片文件不存在: {image_path}")

        # 读取图片
        image = cv2.imread(image_path)
        if image is None:
            raise ValueError(f"无法读取图片: {image_path}")

        image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # cv2.imwrite(MINIPROPATH, image_rgb)
        # 执行推理
        results = self.model(image_rgb,
                             conf=conf_threshold,
                             iou=iou_threshold,
                             device=self.device)

        # 在图片上绘制检测结果
        annotated_image = self.annotate_image(image_rgb.copy(), results)

        return annotated_image, results

    def detect_single_image(self,
                            image_path: str,
                            output_path: Optional[str] = None,
                            conf_threshold: float = CONFTHRESHOLD,
                            iou_threshold: float = IOUTHRESHOLD) -> Dict:
        """
        检测单个图像文件并保存结果
        :param image_path: 输入图像文件路径
        :param output_path: 输出结果保存路径（可选）
        :param conf_threshold: 置信度阈值
        :param iou_threshold: IOU阈值
        :return: 处理结果字典
        """
        # if self.model is None:
        #     raise ValueError("模型未加载，请先调用load_model方法")

        if not os.path.exists(image_path):
            raise FileNotFoundError(f"图片文件不存在: {image_path}")

        print(f"正在处理单个图像文件: {image_path}")
        # 读取原图并调整大小
        original_image = cv2.imread(image_path)
        if original_image is None:
            raise ValueError(f"无法读取图片: {image_path}")
        image_resized = cv2.resize(original_image, (320, 240))
        # 保存原图到 latest 文件
        success = cv2.imwrite(MINIPROLATESTPATH, image_resized)
        print("保存到最新文件：", MINIPROLATESTPATH, success)
        # 使用现有的detect_image方法进行检测
        annotated_image, results = self.detect_image(image_path,
                                                     conf_threshold,
                                                     iou_threshold)
        self.save_box_areas(results, annotated_image.shape[1],
                            annotated_image.shape[0])
        # self.save_box_areas(results, image.shape[1], image.shape[0])
        # 保存结果（如果指定了输出路径）
        if output_path:
            # 创建输出目录（如果不存在）
            # output_dir = os.path.dirname(output_path)
            if output_path and not os.path.exists(output_path):
                os.makedirs(output_path)
            output_folder = output_path
            output_path = os.path.join(
                output_path,
                f"{int(time.time())}_{os.path.basename(image_path)}")
            # 保存结果图像
            self.save_result(annotated_image, output_path)
            if self.count % saveImagePerMin == 0:
                # 创建labelimg文件夹用于保存原图和标签文件
                labelimg_folder = os.path.join(
                    output_folder,
                    "labelimg") if output_folder else "results/labelimg"
                if not os.path.exists(labelimg_folder):
                    os.makedirs(labelimg_folder)
                # output_path = os.path.join(output_folder, f"result_{image_path}")
                # if os.path.exists(output_path):
                #     print(f"结果文件已存在，跳过处理: {image_file}")
                #     continue
                # image_path = os.path.join(folder_path, image_path)
                print(f"正在处理: {image_path}")
                # 将原图移动到labelimg文件夹（剪切操作）
                target_image_path = os.path.join(
                    labelimg_folder,
                    f"{int(time.time())}_{os.path.basename(image_path)}")
                # 如果目标文件已存在，先删除再移动
                if os.path.exists(target_image_path):
                    os.remove(target_image_path)
                shutil.copy(image_path, target_image_path)
                # else:
                # os.rename(image_path, original_image_path)
                # 保存YOLO格式的标签文件
                label_path = os.path.splitext(target_image_path)[0] + ".txt"
                image = cv2.imread(image_path)
                if image is not None:
                    self.save_yolo_labels(results, label_path, image.shape[1],
                                          image.shape[0])
                # self.save_box_areas(results, image.shape[1], image.shape[0])
                print(f"结果已保存到: {output_path}")
                print(f"文件已复制到: {target_image_path}")
                print(f"标签已保存到: {label_path}")
            else:
                print("只处理不保存")
            self.count += 1

        # return {
        #     'input_path': image_path,
        #     'output_path': output_path,
        #     'annotated_image': annotated_image,
        #     'results': results
        # }

    def annotate_image(self, image: np.ndarray, results: Any) -> np.ndarray:
        """
        在图片上绘制检测结果
        :param image: 原始图片
        :param results: 检测结果
        :return: 标注后的图片
        """
        # 参考app.py中的display_detection_results方法
        if not hasattr(results[0], 'boxes') or results[0].boxes is None:
            return image

        boxes = results[0].boxes
        box_corners = []

        for box in boxes:
            x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
            conf = box.conf.cpu().numpy()[0]
            cls = int(box.cls.cpu().numpy()[0])

            # 生成颜色
            color = self.generate_color(cls)

            # 绘制边界框
            cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), color,
                          2)

            # 绘制标签
            class_name = self.class_names[
                cls] if self.class_names else f"Class {cls}"
            label = f"{class_name}: {conf:.2f}"
            cv2.putText(image, label, (int(x1), int(y1) - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)

            # 存储边界框角点信息 (左上角和右下角)
            box_corners.append(((int(x1), int(y1)), (int(x2), int(y2)), cls))

        # 添加连接线：连接在同一条线上的边界框角点
        self.connect_boxes_on_same_line(image, box_corners)

        # 添加总面积信息到图片上
        box_coords = []
        for box in boxes:
            x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
            box_coords.append((int(x1), int(y1), int(x2), int(y2)))

        total_area = self.calculate_non_overlapping_area(box_coords)
        # 获取图像尺寸
        img_height, img_width = image.shape[:2]
        # 计算图像总面积
        img_total_area = img_width * img_height
        # 计算占比
        area_ratio = (total_area /
                      img_total_area) * 100 if img_total_area > 0 else 0
        # 在图像上显示总面积和占比，参考app.py中的实现
        area_text = f"Total Area: {total_area:.0f}px ({area_ratio:.2f}%)"
        cv2.putText(image, area_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
                    (0, 255, 0), 2)

        # 如果有计数线，绘制它
        if self.counting_line and len(self.counting_line) == 4:
            cv2.line(image,
                     (int(self.counting_line[0]), int(self.counting_line[1])),
                     (int(self.counting_line[2]), int(self.counting_line[3])),
                     (0, 0, 255), 2)

        # 显示计数信息
        # cv2.putText(image, f"上行: {self.count_up}", (10, 60),
        #             cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
        # cv2.putText(image, f"下行: {self.count_down}", (10, 90),
        #             cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

        # 显示各类别经过计数线的次数
        y_offset = 120
        for class_name, count in self.class_line_counts.items():
            cv2.putText(image, f"{class_name} 经过: {count}", (10, y_offset),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            y_offset += 30

        return image

    def connect_boxes_on_same_line(
        self, image: np.ndarray, box_corners: List[Tuple[Tuple[int, int],
                                                         Tuple[int, int],
                                                         int]]) -> None:
        """
        连接在同一条线上的边界框角点
        :param image: 图像
        :param box_corners: 边界框角点列表 [((x1, y1), (x2, y2), class_id), ...]
        """
        if len(box_corners) < 2:
            return

        # 按中心点y坐标排序
        sorted_boxes = sorted(box_corners,
                              key=lambda b: (b[0][1] + b[1][1]) / 2)

        # 查找在同一水平线上的点（y坐标相近）
        i = 0
        while i < len(sorted_boxes):
            same_line_boxes = []
            base_center_y = (sorted_boxes[i][0][1] + sorted_boxes[i][1][1]) / 2

            # 收集同一水平线上的所有框
            j = i
            while j < len(sorted_boxes):
                center_y = (sorted_boxes[j][0][1] + sorted_boxes[j][1][1]) / 2
                if abs(center_y - base_center_y) < HLINEDEVIATION:
                    same_line_boxes.append(sorted_boxes[j])
                    j += 1
                else:
                    break

            # 如果同一水平线上有至少两个框，则连接它们
            if len(same_line_boxes) >= 2:
                # 按x坐标排序
                same_line_boxes.sort(key=lambda b:
                                     (b[0][0] + b[1][0]) / 2)  # 按中心x坐标排序

                # 计算最大框宽度
                max_width = 0
                for box in same_line_boxes:
                    (x1, y1), (x2, y2), cls = box
                    width = x2 - x1
                    if width > max_width:
                        max_width = width

                # 构建一个多边形顶点列表
                upper_points = []  # 上边缘点
                lower_points = []  # 下边缘点

                # 收集所有框的上下边缘端点
                for idx, box in enumerate(same_line_boxes):
                    (x1, y1), (x2, y2), cls = box

                    # 如果不是第一个框，检查与前一个框的距离
                    if idx > 0:
                        prev_box = same_line_boxes[idx - 1]
                        prev_x1, prev_y1 = prev_box[0]
                        prev_x2, prev_y2 = prev_box[1]
                        # 计算两个框中心点之间的距离
                        current_center_x = (x1 + x2) / 2
                        prev_center_x = (prev_x1 + prev_x2) / 2
                        distance = abs(current_center_x - prev_center_x)
                        print("当前：", current_center_x, "前个：", prev_center_x,
                              "距离:", distance,"框宽:",max_width)

                        # 如果距离超过最大框宽度的2倍，则跳过这个框
                        if distance > max_width * 2:
                            continue

                    # 收集左上和右上端点作为上边缘点
                    upper_points.append((x1, y1))  # 左上端点
                    upper_points.append((x2, y1))  # 右上端点
                    # 收集左下和右下端点作为下边缘点
                    lower_points.append((x1, y2))  # 左下端点
                    lower_points.append((x2, y2))  # 右下端点

                # 按x坐标排序上下边缘点
                upper_points.sort(key=lambda p: p[0])
                lower_points.sort(key=lambda p: p[0])

                # 生成曲线路径点
                if len(upper_points) >= 2:
                    # 为上边缘生成曲线点
                    upper_curve_points = []
                    for k in range(len(upper_points) - 1):
                        pt1 = upper_points[k]
                        pt2 = upper_points[k + 1]

                        # 创建控制点以生成曲线效果
                        control_x = (pt1[0] + pt2[0]) // 2
                        control_y = min(pt1[1], pt2[1]) - 20  # 曲线向上凸起

                        # 生成贝塞尔曲线点
                        for t in range(0, 11):  # 11个点构成曲线
                            t_norm = t / 10.0
                            x = (1 - t_norm)**2 * pt1[0] + 2 * (
                                1 - t_norm
                            ) * t_norm * control_x + t_norm**2 * pt2[0]
                            y = (1 - t_norm)**2 * pt1[1] + 2 * (
                                1 - t_norm
                            ) * t_norm * control_y + t_norm**2 * pt2[1]
                            upper_curve_points.append((int(x), int(y)))

                    # 绘制上边缘虚曲线
                    for idx in range(len(upper_curve_points) - 1):
                        if idx % 2 == 0:  # 创建虚线效果
                            pt1 = upper_curve_points[idx]
                            pt2 = upper_curve_points[idx + 1]
                            cv2.line(image, pt1, pt2, (255, 255, 255), 2)

                if len(lower_points) >= 2:
                    # 为下边缘生成曲线点
                    lower_curve_points = []
                    for k in range(len(lower_points) - 1):
                        pt1 = lower_points[k]
                        pt2 = lower_points[k + 1]

                        # 创建控制点以生成曲线效果
                        control_x = (pt1[0] + pt2[0]) // 2
                        control_y = max(pt1[1], pt2[1]) + 20  # 曲线向下凸起

                        # 生成贝塞尔曲线点
                        for t in range(0, 11):  # 11个点构成曲线
                            t_norm = t / 10.0
                            x = (1 - t_norm)**2 * pt1[0] + 2 * (
                                1 - t_norm
                            ) * t_norm * control_x + t_norm**2 * pt2[0]
                            y = (1 - t_norm)**2 * pt1[1] + 2 * (
                                1 - t_norm
                            ) * t_norm * control_y + t_norm**2 * pt2[1]
                            lower_curve_points.append((int(x), int(y)))

                    # 绘制下边缘虚曲线
                    for idx in range(len(lower_curve_points) - 1):
                        if idx % 2 == 0:  # 创建虚线效果
                            pt1 = lower_curve_points[idx]
                            pt2 = lower_curve_points[idx + 1]
                            cv2.line(image, pt1, pt2, (255, 255, 255), 2)

                # 绘制最两侧的封闭虚线，与对应方框一致
                # 左侧封闭线
                left_upper = upper_points[0]
                left_lower = lower_points[0]
                self.draw_dashed_line(image, left_upper, left_lower,
                                      (255, 255, 255), 2, 5)

                # 右侧封闭线
                right_upper = upper_points[-1]
                right_lower = lower_points[-1]
                self.draw_dashed_line(image, right_upper, right_lower,
                                      (255, 255, 255), 2, 5)

                # 计算曲线围成的面积并显示百分比
                if len(upper_curve_points) >= 2 and len(
                        lower_curve_points) >= 2:
                    # 构建完整的轮廓点（上曲线 + 右侧线 + 下曲线反向 + 左侧线）
                    contour_points = []

                    # 添加上边缘曲线点
                    contour_points.extend(upper_curve_points)

                    # 添加右侧连接线点
                    contour_points.append(right_lower)
                    contour_points.append(right_upper)

                    # 添加下边缘曲线点（反向添加）
                    contour_points.extend(reversed(lower_curve_points))

                    # 添加左侧连接线点
                    contour_points.append(left_upper)
                    contour_points.append(left_lower)

                    # 转换为numpy数组
                    contour = np.array(contour_points, dtype=np.int32)

                    # 计算轮廓面积
                    area = cv2.contourArea(contour)
                    self.guessarea += int(area)

                    # 获取图像尺寸
                    img_height, img_width = image.shape[:2]
                    img_area = img_width * img_height

                    # 计算占比
                    if img_area > 0:
                        area_ratio = (area / img_area) * 100

                        # 在图像上显示面积占比
                        # 找到轮廓的中心点来放置文本
                        moments = cv2.moments(contour)
                        if moments["m00"] != 0:
                            cx = int(moments["m10"] / moments["m00"])
                            cy = int(moments["m01"] / moments["m00"])
                        else:
                            cx, cy = image.shape[1] // 2, image.shape[0] // 2

                        # 显示面积占比文本
                        area_text = f"{area_ratio:.2f}%"
                        cv2.putText(image, area_text, (cx, cy),
                                    cv2.FONT_HERSHEY_SIMPLEX, 0.6,
                                    (0, 255, 255), 2)

                # 添加标注信息：距离、各点坐标和框宽
                for idx, box in enumerate(same_line_boxes):
                    (x1, y1), (x2, y2), cls = box
                    width = x2 - x1
                    
                    # 标注框的宽度
                    cv2.putText(image, f"W:{width}", (x1, y1 - 25),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 255), 1)
                    
                    # 标注框的坐标
                    cv2.putText(image, f"({x1},{y1})", (x1, y1 - 35),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 255), 1)
                    cv2.putText(image, f"({x2},{y2})", (x2, y2 + 15),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 255), 1)
                    
                    # 如果不是第一个框，计算并标注与前一个框的距离
                    if idx > 0:
                        prev_box = same_line_boxes[idx - 1]
                        prev_x1, prev_y1 = prev_box[0]
                        prev_x2, prev_y2 = prev_box[1]
                        current_center_x = (x1 + x2) / 2
                        prev_center_x = (prev_x1 + prev_x2) / 2
                        distance = abs(current_center_x - prev_center_x)
                        
                        # 在两个框中间位置标注距离
                        mid_x = int((current_center_x + prev_center_x) / 2)
                        mid_y = int((y1 + y2 + prev_y1 + prev_y2) / 4)  # 垂直居中
                        cv2.putText(image, f"D:{distance:.1f}", (mid_x, mid_y),
                                    cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 255), 1)

            i = j

    def draw_dashed_line(self,
                         img,
                         pt1,
                         pt2,
                         color,
                         thickness=1,
                         dash_length=10):
        """绘制虚线"""
        dist = ((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)**.5
        dashes = int(dist // dash_length)
        for i in range(dashes):
            start = (int(pt1[0] + (pt2[0] - pt1[0]) * i / dashes),
                     int(pt1[1] + (pt2[1] - pt1[1]) * i / dashes))
            end = (int(pt1[0] + (pt2[0] - pt1[0]) * (i + 0.5) / dashes),
                   int(pt1[1] + (pt2[1] - pt1[1]) * (i + 0.5) / dashes))
            cv2.line(img, start, end, color, thickness)

    def detect_folder(self,
                      folder_path: str,
                      output_folder: Optional[str] = None,
                      conf_threshold: float = CONFTHRESHOLD,
                      iou_threshold: float = IOUTHRESHOLD) -> List[Dict]:
        """
        识别文件夹中的所有图片
        :param folder_path: 文件夹路径
        :param output_folder: 输出文件夹路径（可选）
        :param conf_threshold: 置信度阈值
        :param iou_threshold: IOU阈值
        :return: 处理结果列表
        """
        if self.model is None:
            raise ValueError("模型未加载，请先调用load_model方法")

        if not os.path.exists(folder_path):
            raise FileNotFoundError(f"文件夹不存在: {folder_path}")

        # 支持的图片格式
        supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif')

        # 获取文件夹中的所有图片文件
        image_files = [
            f for f in os.listdir(folder_path)
            if f.lower().endswith(supported_formats)
        ]

        if not image_files:
            print(f"文件夹 {folder_path} 中没有找到支持的图片文件")
            return []

        results_list = []

        # 创建输出文件夹（如果指定了）
        if output_folder and not os.path.exists(output_folder):
            os.makedirs(output_folder)

        # 创建labelimg文件夹用于保存原图和标签文件
        labelimg_folder = os.path.join(
            output_folder, "labelimg") if output_folder else "results/labelimg"
        if not os.path.exists(labelimg_folder):
            os.makedirs(labelimg_folder)

        print(f"找到 {len(image_files)} 个图片文件，开始处理...")

        for image_file in image_files:
            try:
                # 检查是否已经存在结果文件，如果存在则跳过
                if output_folder:
                    output_path = os.path.join(output_folder,
                                               f"result_{image_file}")
                    # if os.path.exists(output_path):
                    #     print(f"结果文件已存在，跳过处理: {image_file}")
                    #     continue

                image_path = os.path.join(folder_path, image_file)
                print(f"正在处理: {image_file}")

                # 读取图片
                image = cv2.imread(image_path)
                if image is None:
                    print(f"无法读取图片: {image_file}")
                    continue

                image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

                # 执行推理
                results = self.model(image_rgb,
                                     conf=conf_threshold,
                                     iou=iou_threshold,
                                     device=self.device)

                # 更新对象计数（如果设置了计数线）
                if self.counting_line is not None:
                    self.count_objects(results[0])

                # 在图片上绘制检测结果
                annotated_image = self.annotate_image(image_rgb.copy(),
                                                      results)

                # 保存结果（如果指定了输出文件夹）
                if output_folder:
                    output_path = os.path.join(output_folder,
                                               f"result_{image_file}")
                    self.save_result(annotated_image, output_path)

                    # 将原图移动到labelimg文件夹（剪切操作）
                    original_image_path = os.path.join(labelimg_folder,
                                                       image_file)
                    # 如果目标文件已存在，先删除再移动
                    if os.path.exists(original_image_path):
                        os.remove(image_path)
                    else:
                        os.rename(image_path, original_image_path)

                    # 保存YOLO格式的标签文件到labelimg文件夹
                    label_path = os.path.join(
                        labelimg_folder,
                        f"{os.path.splitext(image_file)[0]}.txt")
                    self.save_yolo_labels(results, label_path, image.shape[1],
                                          image.shape[0])

                    # # 计算并保存检测框总面积到box.txt文件
                    # box_area_path = os.path.join(r"D:\boxes", f"{os.path.splitext(image_file)[0]}_box.txt")
                    self.save_box_areas(results, image.shape[1],
                                        image.shape[0])

                    print(f"结果已保存到: {output_path}")
                    print(f"原图已移动到: {original_image_path}")
                    print(f"标签已保存到: {label_path}")
                    # print(f"检测框面积信息已保存到: {box_area_path}")

                results_list.append({
                    'filename': image_file,
                    'image_path': image_path,
                    'annotated_image': annotated_image,
                    'results': results
                })

            except Exception as e:
                print(f"处理图片 {image_file} 时出错: {str(e)}")
                continue

        print(f"处理完成，共处理 {len(results_list)} 个图片文件")
        return results_list

    def generate_color(self, class_id: int) -> Tuple[int, int, int]:
        """
        为不同类别生成固定颜色
        :param class_id: 类别ID
        :return: 颜色 (B, G, R)
        """
        np.random.seed(class_id)
        return tuple(map(int, np.random.randint(0, 255, 3)))

    def save_result(self, image: np.ndarray, save_path: str) -> None:
        """
        保存结果图片
        :param image: 处理后的图片
        :param save_path: 保存路径
        """
        # 转换颜色空间为BGR以正确保存
        if len(image.shape) == 3 and image.shape[2] == 3:
            image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        else:
            image_bgr = image

        # 保存结果图片到result文件
        success = cv2.imwrite(save_path, image_bgr)
        # 保存到发布    原图
        success = cv2.imwrite(MINIPROPREPATH, image_bgr)
        print("保存到发布文件夹")
        # 调整图片大小到320x240
        image_resized = cv2.resize(image_bgr, (320, 240))
        # 保存到发布    缩略图
        cv2.imwrite(MINIPROPATH, image_resized)
        if not success:
            raise ValueError(f"保存图片失败: {save_path}")

    def save_yolo_labels(self, results: Any, label_path: str, img_width: int,
                         img_height: int) -> None:
        """
        保存YOLO格式的标签文件
        :param results: 检测结果
        :param label_path: 标签文件保存路径
        :param img_width: 图片宽度
        :param img_height: 图片高度
        """
        if not hasattr(results[0], 'boxes') or results[0].boxes is None:
            # 如果没有检测到任何对象，创建空的标签文件
            with open(label_path, 'w') as f:
                pass
            return

        boxes = results[0].boxes
        labels = []

        for box in boxes:
            # 获取边界框坐标
            x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()

            # 转换为YOLO格式 (center_x, center_y, width, height) 归一化到0-1
            center_x = ((x1 + x2) / 2) / img_width
            center_y = ((y1 + y2) / 2) / img_height
            width = (x2 - x1) / img_width
            height = (y2 - y1) / img_height

            # 获取类别ID
            cls = int(box.cls.cpu().numpy()[0])

            # 添加到标签列表
            labels.append(
                f"{cls} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}"
            )

        # 保存标签文件
        with open(label_path, 'w') as f:
            f.write('\n'.join(labels))

    def save_box_areas(self, results: Any, img_width: int,
                       img_height: int) -> None:
        """
        计算并保存检测框总面积（重叠部分只计算一次）
        :param results: 检测结果
        :param img_width: 图片宽度
        :param img_height: 图片高度
        """
        if not hasattr(results[0], 'boxes') or results[0].boxes is None:
            # 如果没有检测到任何对象，创建空的面积文件
            # with open(area_path, 'w') as f:
            #     f.write("0\n")
            return

        boxes = results[0].boxes
        box_coords = []

        # 获取所有边界框坐标
        for box in boxes:
            x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
            box_coords.append((int(x1), int(y1), int(x2), int(y2)))

        # 计算总面积（考虑重叠区域）
        total_area = self.calculate_non_overlapping_area(box_coords)
        conn = sqlite3.connect(YOLODBPATH)
        cursor = conn.cursor()
        insert_sql = '''INSERT INTO watergrass (count,wglarea,guessarea,imgarea,timestamp) VALUES (?, ?, ?, ?,?)'''
        cursor.execute(insert_sql,
                       (len(box_coords), total_area, self.guessarea,
                        img_width * img_height, int(time.time())))
        conn.commit()
        conn.close()
        self.guessarea = 0
        print("保存面积信息成功")
        check_watergrass_ratio()
        # 保存面积信息
        # with open(area_path, 'w') as f:
        #     f.write(f"{total_area}\n")
        #     f.write(f"Image dimensions: {img_width}x{img_height}\n")
        #     f.write(f"Number of boxes: {len(box_coords)}\n")
        #     for i, (x1, y1, x2, y2) in enumerate(box_coords):
        #         box_area = (x2 - x1) * (y2 - y1)
        #         f.write(f"Box {i+1}: ({x1},{y1})-({x2},{y2}), Area: {box_area}\n")

    def calculate_non_overlapping_area(
            self, boxes: List[Tuple[int, int, int, int]]) -> int:
        """
        计算非重叠区域的总面积
        :param boxes: 边界框列表 [(x1, y1, x2, y2), ...]
        :return: 非重叠总面积
        """
        if not boxes:
            return 0

        if len(boxes) == 1:
            x1, y1, x2, y2 = boxes[0]
            return (x2 - x1) * (y2 - y1)

        # 过滤掉被其他框完全包含的框
        filtered_boxes = []
        for i, (x1, y1, x2, y2) in enumerate(boxes):
            is_contained = False
            for j, (other_x1, other_y1, other_x2,
                    other_y2) in enumerate(boxes):
                if i != j and other_x1 <= x1 and other_y1 <= y1 and other_x2 >= x2 and other_y2 >= y2:
                    # 当前框被其他框包含
                    is_contained = True
                    break
            if not is_contained:
                filtered_boxes.append((x1, y1, x2, y2))

        # 使用扫描线算法计算总面积
        # 创建事件列表：(x坐标, 类型(0=开始, 1=结束), y1, y2)
        events = []
        for x1, y1, x2, y2 in filtered_boxes:
            events.append((x1, 0, y1, y2))  # 矩形开始
            events.append((x2, 1, y1, y2))  # 矩形结束

        # 按x坐标排序
        events.sort()

        total_area = 0
        active_intervals = []  # 当前活跃的y区间
        last_x = events[0][0]

        for x, event_type, y1, y2 in events:
            # 计算上一个x到当前x之间的面积
            if x > last_x and active_intervals:
                # 计算当前y轴上的覆盖长度
                y_coverage = self.calculate_y_coverage(active_intervals)
                total_area += (x - last_x) * y_coverage

            # 更新活跃区间
            if event_type == 0:  # 矩形开始
                active_intervals.append((y1, y2))
            else:  # 矩形结束
                if (y1, y2) in active_intervals:
                    active_intervals.remove((y1, y2))

            last_x = x

        return total_area

    def calculate_y_coverage(self, intervals: List[Tuple[int, int]]) -> int:
        """
        计算y轴区间覆盖的总长度
        :param intervals: y轴区间列表 [(y1, y2), ...]
        :return: 覆盖的总长度
        """
        if not intervals:
            return 0

        # 对区间按起始位置排序
        sorted_intervals = sorted(intervals)

        total_length = 0
        current_start, current_end = sorted_intervals[0]

        for start, end in sorted_intervals[1:]:
            if start <= current_end:  # 区间重叠
                current_end = max(current_end, end)
            else:  # 区间不重叠
                total_length += current_end - current_start
                current_start, current_end = start, end

        total_length += current_end - current_start
        return total_length

    def detect_image_from_array(
            self,
            image_array: np.ndarray,
            conf_threshold: float = CONFTHRESHOLD,
            iou_threshold: float = IOUTHRESHOLD) -> Tuple[np.ndarray, Any]:
        """
        从numpy数组识别图片
        :param image_array: 图片numpy数组
        :param conf_threshold: 置信度阈值
        :param iou_threshold: IOU阈值
        :return: 处理后的图片和检测结果
        """
        if self.model is None:
            raise ValueError("模型未加载，请先调用load_model方法")

        image_rgb = cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB)

        # 执行推理
        results = self.model(image_rgb,
                             conf=conf_threshold,
                             iou=iou_threshold,
                             device=self.device)

        # 在图片上绘制检测结果
        annotated_image = self.annotate_image(image_rgb.copy(), results)

        return annotated_image, results


def watchYOLOimages(yolo, model_path, folder_path, output_folder, conf_thresh,
                    iou_thresh):
    # 创建YOLO实例
    yolo = MyYOLO()
    try:
        # 加载模型
        # model_path = r"D:\YOLO11 V2.0\models\best_0829_640.pt"  # 根据实际情况修改模型路径
        if os.path.exists(model_path):
            yolo.load_model(model_path)
        else:
            print(f"模型文件 {model_path} 不存在，请先下载模型")
            sys.exit(1)

        # 检测文件夹中的所有图片
        # folder_path = r"D:\yoloimages"  # 根据实际情况修改文件夹路径
        # output_folder = r"D:\results"    # 结果保存文件夹

        if not os.path.exists(folder_path):
            print(f"测试文件夹 {folder_path} 不存在")
            sys.exit(1)

        print("开始识别文件夹中的图片...")
        yolo.detect_folder(folder_path, output_folder, conf_thresh, iou_thresh)
        print("文件夹图片识别完成")

    except Exception as e:
        print(f"发生错误: {str(e)}")
        # sys.exit(1)


def watchSingleYOLOimage(yolo: MyYOLO,
                         image_path,
                         output_folder,
                         conf_thresh=0.2,
                         iou_thresh=0.5):
    try:
        if not os.path.exists(image_path):
            print(f"测试文件 {image_path} 不存在")
            sys.exit(1)

        print("开始识别图片...")
        yolo.detect_single_image(image_path, output_folder, conf_thresh,
                                 iou_thresh)
        print("图片识别完成")

    except Exception as e:
        print(f"发生错误: {str(e)}")
        # sys.exit(1)


def yolo_init(model_path):
    yolo = MyYOLO()
    if os.path.exists(model_path):
        yolo.load_model(model_path)
    else:
        print(f"模型文件 {model_path} 不存在，请先下载模型")
        sys.exit(1)
    return yolo


# 使用示例
if __name__ == "__main__":
    '''
    
    '''
    # 创建YOLO实例
    # print("主要参数如下")
    # print("监视文件名：",TARGETFILENAMES)
    # print("监视文件夹：",folder_path)
    # print("输出文件夹",output_folder)
    # print("模型文件：",model_path)
    yolo = yolo_init(MODELPATH)
    try:
        yolo.detect_single_image(
            r'D:\myGitee\control-net\data\onecloud\temp_2.jpg', r"D:\results")

        # 设置计数线 (从点(100, 300)到点(500, 300)的水平线)
        # yolo.set_counting_line(100, 300, 500, 300)

        # 或者可以设置其他方向的计数线
        # yolo.set_counting_line(100, 0, 100, 500)  # 垂直线
        # yolo.set_counting_line(100, 100, 500, 500)  # 对角线

        # # 获取并打印统计信息
        # stats = yolo.get_statistics()
        # print(f"统计信息:")
        # print(f"  上行计数: {stats['count_up']}")
        # print(f"  下行计数: {stats['count_down']}")
        # print(f"  各类别经过计数线次数:")
        # for class_name, count in stats['class_line_counts'].items():
        #     print(f"    {class_name}: {count}")

        # # 如果需要清除计数线，可以调用:
        # # yolo.clear_counting_line()

    except Exception as e:
        print(f"发生错误: {str(e)}")
        sys.exit(1)
